var FileUpload = new Class({
	Implements: [Options,Events],
	options: {
		limit: false,
		url: ''
	},
	files: [],
	initialize: function(input,options) {
		var self = this;
		this.setOptions(options);
		this.fileControl = $(input);
		this.name = this.fileControl.get('name').replace(/\[\w*]\s*$/g,'');
		this.form = this.fileControl.getParent('form');
		with (window.location) {
			this.options.url = this.options.url
				|| this.form.get('action')
				|| protocol + '//' + host + (port ? ':' + port : '') + pathname;
		}
		
		this.form.set('send', {url: this.options.url});
		
		// Attach events
		this.fileControl.addEvent('change',this.queueFile.bind(this));
		
		// Find upload button
		if (this.fileControl.getNext() && this.fileControl.getNext().get('name').test('_file_upload_')) {
			this.uploadControl = this.fileControl.getNext();
		} else {
			this.uploadControl = new Element('button',{
				name: '_' + this.name + '_upload_',
				value: 'Upload'
			}).inject(this.fileControl,'after');
		}
		
		// Hide upload button till use selects any files & attach event listeners
		this.uploadControl
			.addEvent('click',function(e) {
				new Event(e).preventDefault();
				self.upload();
			})
			.setStyle('visibility','hidden');
		
		// Find file list
		if (this.fileControl.getPrevious() && 'ul' === this.fileControl.getPrevious().get('tag')) {
			this.fileList = this.fileControl.getPrevious();
			this.fileList.getElements('button[name=_file_delete_]').addEvent('click',this.onRemove.bindWithEvent(this));
		} else {
			this.fileList = new Element('ul',{
				'class': 'files'
			}).inject(this.fileControl,'before');
		}
		
		this.checksum = this.form.getElement('input[name=_' + this.name + '_cs_]');
	},
	queueFile: function(e) {
		var input = new Event(e).target,
			clone = input.clone(),
			offset = this.fileList.getChildren().length;
		
		// Normalize for all browsers
		if (!input.files)
			input.files = [input];
		
		// item() method is required by some browsers, but not provided by others
		if (!input.files.item)
			input.files.item = function(i) { return this[i]; }
		
		// splice() method for file removal
		if (!input.files.splice)
			input.files.splice = function(i) { delete this[i]; }
		
		// Process each file (webkit- and mozilla-based browsers support selecting multiple files)
		for (var i=0; i < input.files.length; i++) {
			// Some browsers don't expose fileName property. We'll have to cheat a little.
			var name   = input.files.item(i).fileName || (input.files.item(i).fileName = input.files.item(i).get('value').split("\\").pop()),
				ext     = -1 !== name.indexOf('.') ? name.split('.').pop().toLowerCase() : 'unknown';
			
			// Add list item to file list
			input.files.item(i).itemNumber = offset + i;
			input.files.item(i).listItem = new Element('li',{
				'class': 'incomplete ' + ext,
				text: name + ' '
			}).inject(this.fileList).adopt(
				// Inject delete button
				input.files.item(i).deleteButton = new Element('button',{
					name: '_file_delete_',
					value: offset + i,
					html: '&#9747;',
					events: {
						click: this.onRemove.bind(this)
					}
				})
			);
			
			// Append file size
			// if (input.files.item(i).fileSize) {
			// 	new Element('span',{
			// 		'class' : 'file-size',
			// 		text : '(' + this.size(input.files.item(i).fileSize) + ')'
			// 	}).inject(input.files.item(i).listItem,'bottom');
			// }
		}
		
		// Save this input for uploading stage
		this.files.push(input);
	
		// Replace input with a full clone, empty the value & get ready for selecting another file
		clone.cloneEvents(input).replaces(input.erase('id').removeEvents()).removeProperty('value');
		
		this.uploadControl.setStyle('visibility','visible');
	},
	remove: function(button) {
		var self = this,
			location = button.getParent().getChildren('input[name*=location]'),
			remove = button.get('value');
		
		if (location && location.get('value') == remove) {
			var data = $H({
				'_remote_upload_' : this.name,
				'_file_delete_' : remove
			}).set(this.checksum.get('name'), this.checksum.get('value'));
			
			this.form.getElements('input[type=hidden][name^='+this.name+']').each(function(el) {
				data.set(el.get('name'),el.get('value'));
			});
			
			new Request({
				url: this.options.url,
				method: 'post',
				data: data,
				onComplete: function(data) {
					self.fileDeleted(data,remove);
				}
			}).send();
			
		} else {
			remove = remove.toInt();
			
			for (var i=0; i < this.files.length; i++) {
				for (var j=0, files = this.files[i].files; j < files.length; j++) {
					if (files.item(j).itemNumber === remove) {
						files.item(j).listItem.destroy();
						return;
					}
				}
			}
		}
	},
	upload: function() {
		// If no files (left) to upload, exit.
		if (this.files.length == 0) {
			this.fireEvent('complete');
			return;
		}
		
		var input = this.files.shift(),
			uid = ['AjaxUpload', (new Date).getTime(), String(Math.random()).substring(2)].join('_'),
			iframe = new Element('iframe',{
				id: uid,
				name: uid,
				src: 'about:blank',
				styles: {
					position: 'absolute',
					left: -10000,
					right: -10000
				}
			}).inject(document.body),
			form = new Element('form',{
				enctype: 'multipart/form-data',
				action: this.options.url,
				target: uid,
				method: 'post',
				styles: {
					display: 'none'
				}
			}).inject(document.body).adopt(input);
			
			this.appendFileData(form);
			
			input.store('uid',uid);
			input.store('iframe',iframe);
			input.store('form',form);
			
			iframe.addEvent('load',this.fileUploaded.bind(this,input));
			form.submit();
	},
	appendFileData: function(form) {
		form.adopt(new Element('input',{
			type: 'hidden',
			name: '_remote_upload_',
			value: this.name
		}), new Element('input',{
			type: 'hidden',
			name: '_file_upload_',
			value: 'Upload'
		}),
		this.checksum.clone());
		
		form.adopt(this.form.getElements('input[type=hidden][name^='+this.name+']').clone());
	},
	findFileData: function(name,data) {
		for (var i=0; i < data.files.length; i++) {
			if (data.files[i].name == name || data.files[i].location == name) {
				return data.files.splice(i,1)[0];
			}
		}
	},
	fileUploaded: function(input){
		var self = this,
			data = JSON.decode($(input.retrieve('iframe').contentWindow.document.body).get('html'));
		
		// Replace each list item with data
		if ($type(data.files) === 'array' && data.files.length > 0) for (var i=0; i < input.files.length; i++) {
			var file = input.files.item(i),
				fileData = this.findFileData(file.fileName,data);
			
			if ($type(fileData) == 'object') {
				file.listItem.removeClass('incomplete');
				$H(fileData).each(function(v,k) {
					new Element('input',{
						type: 'hidden',
						name: self.name+'['+file.itemNumber+']'+'['+k+']',
						value: v
					}).inject(file.listItem,'top');
				});
				file.deleteButton.set('value',fileData.location);
			} else {
				file.listItem.erase();
			}
		}
		
		// Update checksum
		this.checksum.set('value',data.checksum);
		
		//Upload next
		this.upload();
	},
	fileDeleted: function(data,remove) {
		data = JSON.decode(data);
		
		if (this.findFileData(remove,data))
			return;
		
		this.fileList.getElements('input[type=hidden][name*=location]').each(function(el) {
			if (el.get('value') === remove)
				el.getParent().destroy();
		});
		
		this.checksum.set('value',data.checksum);
	},
	size: function(bytes) {
		var prefixes = [' bytes', ' Kb', ' Mb', ' Gb', ' Tb'],
			i = 0;
		
		while (1023 < bytes) {
			if (i + 1 == prefixes.length) break;
			i++;
			bytes /= 1024;
		}
		return  (i ? bytes.toFixed(2) : bytes) + prefixes[i];
	},
	onRemove: function(e) {
		this.remove(new Event(e).preventDefault().target);
	}
});